package awk.thread.future;

import awk.thread.ThreadPoolUtils;

import java.util.ArrayDeque;
import java.util.Queue;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
import java.util.concurrent.ThreadPoolExecutor;

/**
 * @description:
 * @author: wanghaifeng
 * @date: 2021/3/5
 * Runnable接口有个问题，它的方法没有返回值。如果任务需要一个返回结果，那么只能保存到变量，还要提供额外的方法读取，非常不便。所以，Java标准库还提供了一个Callable接口，
 * 和Runnable接口比，它多了一个返回值。
 * 当我们提交一个Callable任务后，我们会同时获得一个Future对象，然后，我们在主线程某个时刻调用Future对象的get()方法，就可以获得异步执行的结果。
 * 在调用get()时，如果异步任务已经完成，我们就直接获得结果。如果异步任务还没有完成，那么get()会阻塞，直到任务完成后才返回结果。
 * <p>
 * 一个Future<V>接口表示一个未来可能会返回的结果，它定义的方法有：
 * get()：获取结果（可能会等待）
 * get(long timeout, TimeUnit unit)：获取结果，但只等待指定的时间；
 * cancel(boolean mayInterruptIfRunning)：取消当前任务；
 * isDone()：判断任务是否已完成。
 */
public class FutureTest {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ThreadPoolExecutor pool = ThreadPoolUtils.getPool();
        //Callable<Double> task = new Task(); Task.call方法为简便起见，直接返回task对象本身了，其中的逻辑修改了taskName
        Task task1 = new Task("小明", 1,20);

        FutureTask<Task> futureTask1 = new FutureTask<>(()->new Task("小明", 1,20));
        //可以使用 executor.submit 执行(推荐),pool.submit()接收callable任务，也可以把callable封装成 FutureTask
        // 如果提交的任务是Callable类型，此时从submit的返回值Future.get()获取返回值，
        // 如果提交的任务是FutureTask类型，从提交的任务FutureTask也可以获取返回值，
        //其实两者是一样的, ThreadPoolExecutor.submit 内部把FutureTask封装为 Future
        //这里可以改进，因为提交一个任务然后get一个结果，而get结果是异步阻塞的，在get之前下面的submit()/get()不会执行。
        pool.submit(futureTask1);

        //从Future获取异步执行返回的结果:
        //1、调用阻塞方法get()，
        //2、轮询看isDone()是否为true，这两种方法都不是很好，因为主线程也会被迫等待。
        //3、将任务放到阻塞队列中，轮询队列，完成的任务从队列中取出，否则再放回队列。
        //4、从Java 8开始引入了 CompletableFuture，它针对Future做了改进，可以传入回调对象，当异步任务完成或者发生异常时，自动调用回调对象的回调方法。

        //ArrayDeque双端队列，底层通过循环数组实现 俩个重要属性 head tail
        Queue<FutureTask<Task>> taskQueue = new ArrayDeque<>();
        taskQueue.add(futureTask1);

        String task1Result = null;
        while (!taskQueue.isEmpty()) {
            try {
                //多个任务，需要认领各自的结果，此时怎么区分哪个任务？解决方案：提交任务时设置任务id，返回的结果也设置对应的任务id
                //pop和poll都是移除并返回队列的队首元素，按照先入队的先出队的顺序(因为会移除，队列不为空时while循环不会结束,从而达到while循环阻塞的目的)；
                //如果deque是空的(Empty) poll方法会返回null， pop方法会抛出异常NoSuchElementException()
                //task2先执行完毕,怎么不让task1阻塞获取task2的结果（可能task2后放入队列，）
                //此处之所以会阻塞，是因为从队首取到的是task1(生产环境谁都不知道耗时的任务在哪个位置)
                //task1还未运行完毕，此时调用get()方法会阻塞；而task2虽然执行完毕，但是在队列的第二个位置
                //解决方案，如果从队首取到的任务还未执行完毕 future.isDone()=false,则重新放回队列，此时该任务就会自动放到队列后面(先进先出)
                FutureTask<Task> future = taskQueue.poll();
                if (!future.isDone()){
                    //任务还在执行，再重新放回队列
                    taskQueue.add(future);
                }else {
                    //任务已经完成,则调用get()方法获取结果
                    Task result = future.get();
                    System.out.println("I get you.....,taskId=" + result.getTaskId());
                    //此处不能直接赋值，需要先校验taskId
                    if (result.getTaskId().equals(task1.getTaskId())){
                        task1Result = result.taskName;
                    }else {
                        //不是自己的结果，再放回去
                        taskQueue.add(future);
                    }
                }

            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        /**
         * ==========================================
         * 小红小红 doing something.....,taskId=2
         * 小明小明 doing something.....,taskId=1
         * I get you.....,taskId=1
         * I get you.....,taskId=2
         * task1Result=小红小红
         * I am main thread,I am going.....
         * ==========================================
         * 执行结果分析：task2耗时3秒，先执行结束，task1继续执行，在所有的task执行完毕之前，while一直阻塞。
         * task1一直阻塞，导致已经完毕的task2无法打印[I get you.....,taskId=]
         * 因为 task1Result 未校验taskId，导致task2的结果给了task1，获取到错误的结果。
         *
         */

        task1.setResult(task1Result);
        System.out.println("task1Result=" +task1Result);

        System.out.println("I am main thread,I am going.....");
///////////////////////////////////此处任务执行完毕主线程还存活着，因为线程池还维护者存活的线程//////////////////////////////////////////////
        //此处强制关闭线程池，则主线程会停止
        if (pool.getTaskCount() == 0 || pool.getQueue().isEmpty()) {
            pool.shutdown();
        }
    }

    static class Task implements Callable<Task> {
        private String taskName;
        private Integer taskId;
        private Integer costTime;
        private String result;

        public Task(String taskName, Integer taskId,Integer costTime) {
            this.taskName = taskName;
            this.taskId = taskId;
            this.costTime = costTime;
        }

        /**
         * call方法为简便起见，直接返回task对象了
         * @return
         */
        @Override
        public Task call() {
            try {
                Thread.sleep(costTime * 1000);
                //执行任务，修改名称
                taskName += taskName;
                System.out.println(taskName + " doing something.....,taskId=" + taskId);
            } catch (InterruptedException e) {
            }
            return this;
        }

        public String getTaskName() {
            return taskName;
        }

        public void setTaskName(String taskName) {
            this.taskName = taskName;
        }

        public Integer getTaskId() {
            return taskId;
        }

        public void setTaskId(Integer taskId) {
            this.taskId = taskId;
        }

        public Integer getCostTime() {
            return costTime;
        }

        public void setCostTime(Integer costTime) {
            this.costTime = costTime;
        }

        public String getResult() {
            return result;
        }

        public void setResult(String result) {
            this.result = result;
        }
    }

}
